﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BFlatCompile.Nodes;

namespace BFlatCompile.Serializer
{
	public static class ByteCodeExprSerializer
	{
		public static void Serialize(Compiler c, Buffer b, Expression expr)
		{
			if (expr is NullConstant) SerializeNull(c, b);
			else if (expr is AdditionChain) SerializeAddition(c, b, (AdditionChain)expr);
			else if (expr is BaseClassConstructorCall) SerializeBaseClassConstructorCall(c, b, (BaseClassConstructorCall)expr);
			else if (expr is BooleanCombinator) SerializeBooleanCombinator(c, b, (BooleanCombinator)expr);
			else if (expr is BooleanConstant) SerializeBoolean(c, b, (BooleanConstant)expr);
			else if (expr is BooleanNot) SerializeBooleanNot(c, b, (BooleanNot)expr);
			else if (expr is BracketIndex) SerializeBracketIndex(c, b, (BracketIndex)expr, false);
			else if (expr is DotDereference) SerializeDotDereference(c, b, (DotDereference)expr, false);
			else if (expr is EqualityComparison) SerializeEqualityComparison(c, b, (EqualityComparison)expr);
			else if (expr is FunctionCall) SerializeFunctionCall(c, b, (FunctionCall)expr);
			else if (expr is Increment) SerializeIncrement(c, b, (Increment)expr);
			else if (expr is ListDefinition) SerializeListDefinition(c, b, (ListDefinition)expr);
			else if (expr is MapDefinition) SerializeMapDefinition(c, b, (MapDefinition)expr);
			else if (expr is MultiplicationChain) SerializeMultiplication(c, b, (MultiplicationChain)expr);
			else if (expr is NativeCodeInsertion) SerializeNativeCodeInsertion(c, b, (NativeCodeInsertion)expr);
			else if (expr is NegativeSign) SerializeNegativeSign(c, b, (NegativeSign)expr);
			else if (expr is NumericalConstant) SerializeNumber(c, b, (NumericalConstant)expr);
			else if (expr is StringConstant) SerializeString(c, b, (StringConstant)expr);
			else if (expr is Ternary) SerializeTernary(c, b, (Ternary)expr);
			else if (expr is ThisValue) SerializeThisValue(c, b, (ThisValue)expr);
			else if (expr is Variable) SerializeVariable(c, b, (Variable)expr, false);
			else
			{
				throw new Exception("Serializer missing for " + expr.GetType().ToString());
			}
		}

		private static void SerializeBooleanNot(Compiler c, Buffer b, BooleanNot expr)
		{
			Serialize(c, b, expr.Expression);
			b.Add(ByteCodes.BOOLEAN_NOT(c), expr.Token);
		}

		private static void SerializeMapDefinition(Compiler c, Buffer b, MapDefinition expr)
		{
			Expression[] keys = expr.Keys;
			Expression[] values = expr.Values;
			for (int i = 0; i < keys.Length; ++i)
			{
				Serialize(c, b, keys[i]);
				Serialize(c, b, values[i]);
			}
			List<Token> tokens = new List<Token>() { expr.Token };
			tokens.AddRange(expr.KeyTokens);
			b.Add(ByteCodes.CREATE_MAP(c, keys.Length), tokens.ToArray());
		}

		private static void SerializeBaseClassConstructorCall(Compiler c, Buffer b, BaseClassConstructorCall expr)
		{
			foreach (Expression arg in expr.Args)
			{
				Serialize(c, b, arg);
			}

			Constructor cons = null;
			Class baseClassWalker = expr.BaseClass;
			while (cons == null && baseClassWalker != null)
			{
				foreach (Constructor constructor in baseClassWalker.Constructors)
				{
					if (!constructor.IsStatic)
					{
						cons = constructor;
						break;
					}
				}

				baseClassWalker = baseClassWalker.BaseClassInstance;
			}

			if (cons == null)
			{
				throw new ParserException("There is no base constructor to call");
			}

			b.Add(ByteCodes.BASE_CLASS_CONS(c, expr.Args.Length, cons.ID));
		}

		private static void SerializeNativeCodeInsertion(Compiler c, Buffer b, NativeCodeInsertion expr)
		{
			foreach (Expression arg in expr.Args)
			{
				Serialize(c, b, arg);
			}
			b.Add(ByteCodes.NATIVE_FUNCTION(c, NativeCodeLookup.Get(expr.ID), expr.Args.Length));
		}

		private static void SerializeFunctionCall(Compiler c, Buffer b, FunctionCall expr)
		{
			Serialize(c, b, expr.Root);
			int paramCount = expr.Params.Length;
			for (int i = 0; i < paramCount; ++i)
			{
				Serialize(c, b, expr.Params[i]);
			}
			if (expr.IsConstructor)
			{
				b.Add(ByteCodes.CONSTRUCTOR_CALL(c, paramCount), expr.Token);
			}
			else
			{
				b.Add(ByteCodes.FUNCTION_CALL(c, paramCount), expr.Token);
			}
		}

		private static void SerializeThisValue(Compiler c, Buffer b, ThisValue expr)
		{
			b.Add(ByteCodes.THIS(c), expr.Token);
		}

		private static void SerializeEqualityComparison(Compiler c, Buffer b, EqualityComparison expr)
		{
			Expression[] expressions = expr.Expressions;
			string[] ops = expr.Ops;

			Serialize(c, b, expressions[0]);
			for (int i = 0; i < ops.Length; ++i)
			{
				Serialize(c, b, expressions[i + 1]);
				switch (ops[i])
				{
					case "==": b.Add(ByteCodes.EQUALS(c), expr.Tokens[i]); break;
					case "!=": b.Add(ByteCodes.NOT_EQUAL(c), expr.Tokens[i]); break;
					case "<": b.Add(ByteCodes.LESS_THAN(c), expr.Tokens[i]); break;
					case ">": b.Add(ByteCodes.GREATER_THAN(c), expr.Tokens[i]); break;
					case "<=": b.Add(ByteCodes.LESS_THAN_OR_EQUAL(c), expr.Tokens[i]); break;
					case ">=": b.Add(ByteCodes.GREATER_THAN_OR_EQUAL(c), expr.Tokens[i]); break;
					default: throw new Exception("¿Donde Estoy?");
				}
			}
		}

		private static void SerializeBooleanCombinator(Compiler c, Buffer b, BooleanCombinator expr)
		{
			Serialize(c, b, expr.Expressions[0]);
			string[] ops = expr.Ops;

			Buffer output = new Buffer();
			Buffer tb1;
			Buffer tb2;
			Token[] opTokens = expr.Tokens;

			for (int i = ops.Length - 1; i >= 0; --i)
			{
				Expression expression = expr.Expressions[i + 1];
				string op = ops[i];

				tb1 = new Buffer();

				if (op == "&&")
				{
					// && - [ if false, leave false, jump to end ] [ pop ] [ expression ]
					tb1.Add(ByteCodes.POP(c));
					Serialize(c, tb1, expression);
					tb1.Add(output);
					tb2 = new Buffer();
					tb2.Add(ByteCodes.JUMP_IF_FALSE_NO_POP(c, tb1.Size), opTokens[i]);
					tb2.Add(tb1);
					output = tb2;
				}
				else if (op == "||")
				{
					// || - [ if true, leave true, jump to end ] [ pop ] [ expression ]
					tb1.Add(ByteCodes.POP(c));
					Serialize(c, tb1, expression);
					tb1.Add(output);
					tb2 = new Buffer();
					tb2.Add(ByteCodes.JUMP_IF_TRUE_NO_POP(c, tb1.Size), opTokens[i]);
					tb2.Add(tb1);
					output = tb2;
				}
				else if (op == "^^")
				{
					// ^^ - [ expression ] [ xor ]
					Serialize(c, tb1, expression);
					tb1.Add(ByteCodes.XOR(c), opTokens[i]);
					tb1.Add(output);
					output = tb1;
				}
				else
				{
					throw new Exception("This will never happen");
				}
			}

			b.Add(output);
		}

		private static void SerializeNegativeSign(Compiler c, Buffer b, NegativeSign expr)
		{
			Serialize(c, b, expr.Expression);
			b.Add(ByteCodes.NEGATIVE(c), expr.Token);
		}

		private static void SerializeListDefinition(Compiler c, Buffer b, ListDefinition expr)
		{
			for (int i = 0; i < expr.Items.Length; ++i)
			{
				Expression expression = expr.Items[i];
				Serialize(c, b, expression);
			}

			// token will never be necessary here, methinks
			b.Add(ByteCodes.CREATE_LIST(c, expr.Items.Length));
		}

		private static void SerializeIncrement(Compiler c, Buffer b, Increment expr)
		{
			if (expr.Expression is Variable)
			{
				SerializeVariable(c, b, (Variable)expr.Expression, true);
			}
			else if (expr.Expression is DotDereference)
			{
				SerializeDotDereference(c, b, (DotDereference)expr.Expression, true);
			}
			else if (expr.Expression is BracketIndex)
			{
				SerializeBracketIndex(c, b, (BracketIndex)expr.Expression, true);
			}
			else
			{
				throw new Exception("This will never happen.");
			}

			if (expr.IsPrefix)
			{
				b.Add(ByteCodes.INCREMENT_PREFIX(c, expr.Sign == 1 ? 1 : 0), expr.Token);
			}
			else
			{
				b.Add(ByteCodes.INCREMENT_POSTFIX(c, expr.Sign == 1 ? 1 : 0), expr.Token);
			}
		}

		private static void SerializeNumber(Compiler c, Buffer b, NumericalConstant expr)
		{
			if (expr.IsInteger)
			{
				b.Add(ByteCodes.INTEGER(c, expr.Index));
			}
			else // float
			{
				b.Add(ByteCodes.FLOAT(c, expr.Index));
			}
		}

		private static void SerializeMultiplication(Compiler c, Buffer b, MultiplicationChain expr)
		{
			Serialize(c, b, expr.Expressions[0]);
			char[] ops = expr.Ops;
			Expression[] expressions = expr.Expressions;
			Token[] opTokens = expr.Tokens;
			char op;
			for (int i = 0; i < ops.Length; ++i)
			{
				Serialize(c, b, expressions[i + 1]);
				op = ops[i];
				if (op == '*')
				{
					b.Add(ByteCodes.MULTIPLY(c), opTokens[i]);
				}
				else if (op == '/')
				{
					b.Add(ByteCodes.DIVIDE(c), opTokens[i]);
				}
				else if (op == '%')
				{
					b.Add(ByteCodes.MOD(c), opTokens[i]);
				}
				else
				{
					throw new Exception("Unrecognized op: " + op);
				}
			}
		}

		public static void SerializeBracketIndex(Compiler c, Buffer b, BracketIndex expr, bool useWrapper)
		{
			Serialize(c, b, expr.Root);
			Serialize(c, b, expr.Index);
			b.Add((useWrapper ? ByteCodes.BRACKET_WRAPPED(c) : ByteCodes.BRACKET(c)), expr.Token);
		}

		public static void SerializeDotDereference(Compiler c, Buffer b, DotDereference expr, bool useWrapper)
		{
			if (expr.IsThis)
			{
				if (useWrapper)
				{
					throw new Exception("Cannot overwrite class.");
				}

				// TODO: After effective expression is done, this should all go away.
				b.Add(ByteCodes.THIS(c));
			}
			else if (expr.StaticReference != null)
			{
				// This is ugly. TODO: Make it pretty.
				if (expr.StaticReference is Field && expr.Root is Variable && ((Variable)expr.Root).StaticReference is Class)
				{
					Class cls = (Class)((Variable)expr.Root).StaticReference;
					Field field = (Field)expr.StaticReference;
					b.Add(ByteCodes.CLASS(c, cls.ID));

					if (useWrapper)
					{
						b.Add(ByteCodes.DOT_WRAPPED(c, field.NameIndex));
					}
					else
					{
						b.Add(ByteCodes.DOT(c, field.NameIndex));
					}
					return;
				}

				if (expr.StaticReference is Class)
				{
					if (useWrapper)
					{
						throw new Exception("Cannot overwrite class.");
					}

					Class cls = (Class)expr.StaticReference;

					// Here as well
					b.Add(ByteCodes.CLASS(c, cls.ID));
					return;
				}
				else if (expr.StaticReference is Namespace)
				{
					throw new Exception("Cannot evaluate namespace.");
				}
			}

			Serialize(c, b, expr.Root);
			if (useWrapper)
			{
				b.Add(ByteCodes.DOT_WRAPPED(c, expr.StepIndex), expr.Token);
			}
			else
			{
				b.Add(ByteCodes.DOT(c, expr.StepIndex), expr.Token);
			}
		}

		public static void SerializeVariable(Compiler c, Buffer b, Variable expr, bool useWrapper)
		{
			if (expr.IsThis)
			{
				if (useWrapper)
				{
					throw new Exception("Cannot overwrite class.");
				}

				b.Add(ByteCodes.THIS(c), expr.Token);
			}
			else if (expr.StaticReference != null)
			{
				if (useWrapper)
				{
					throw new Exception("Cannot overwrite class.");
				}

				Class cls = expr.StaticReference as Class;
				if (cls == null)
				{
					throw new Exception("Cannot evaluate namespace.");
				}

				// Need token, but this code should go away anyway after effective value is implemented
				b.Add(ByteCodes.CLASS(c, cls.ID));
			}
			else
			{
				if (useWrapper)
				{
					b.Add(ByteCodes.VARIABLE_WRAPPED(c, expr.Index), expr.Token);
				}
				else
				{
					b.Add(ByteCodes.VARIABLE(c, expr.Index), expr.Token);
				}
			}
		}

		private static void SerializeString(Compiler c, Buffer b, StringConstant expr)
		{
			// TODO: will this token ever be needed?
			b.Add(ByteCodes.STRING(c, expr.Index), expr.Token);
		}

		private static void SerializeTernary(Compiler c, Buffer b, Ternary expr)
		{
			// [condition] [jump if true (skip the false value)] [false value (includes jump the true value)] [true value]

			Expression condition = expr.Condition;
			Expression trueValue = expr.TrueValue;
			Expression falseValue = expr.FalseValue;
			Buffer trueBuffer = new Buffer();
			Buffer falseBuffer = new Buffer();
			Serialize(c, trueBuffer, trueValue);
			Serialize(c, falseBuffer, falseValue);
			Serialize(c, b, condition);
			falseBuffer.Add(ByteCodes.JUMP(c, trueBuffer.Size));
			b.Add(ByteCodes.JUMP_IF_TRUE(c, falseBuffer.Size), expr.QuestionMarkToken);
			b.Add(falseBuffer);
			b.Add(trueBuffer);
		}

		private static void SerializeAddition(Compiler c, Buffer b, AdditionChain expr)
		{
			Expression[] expressions = expr.Expressions;
			char[] ops = expr.Ops;
			Serialize(c, b, expressions[0]);
			for (int i = 0; i < ops.Length; ++i)
			{
				Serialize(c, b, expressions[i + 1]);
				b.Add(ops[i] == '+' ? ByteCodes.ADD(c) : ByteCodes.SUBTRACT(c), expr.Tokens[i]);
			}
		}

		private static void SerializeBoolean(Compiler c, Buffer b, BooleanConstant expr)
		{
			b.Add(expr.Value ? ByteCodes.TRUE(c) : ByteCodes.FALSE(c)); // null token - nothing can go wrong
		}

		private static void SerializeNull(Compiler c, Buffer b)
		{
			b.Add(ByteCodes.NULL(c)); // null token - nothing can go wrong
		}
	}
}
